package org.esgl3d.scene;

import java.util.ArrayList;

import org.esgl3d.Mesh;
import org.esgl3d.math.Matrix;
import org.esgl3d.primitives.Pyramid;
import org.esgl3d.rendering.Renderer;

import com.sun.org.apache.xpath.internal.axes.ChildIterator;

/**
 * SceneNode represents a single node of a scene graph. SceneNode can have
 * any number of children attached to it.
 * 
 * @author michael
 */
public class SceneNode {
	
	private Matrix localTransformation = new Matrix();
	private Matrix worldTransformation = new Matrix();
	private boolean isDirty = false;
	
	private SceneNode parentNode;
	private Scene owner;
	
	private ArrayList<SceneNode> childNodes = new ArrayList<SceneNode>();
	
	void setScene(Scene newScene) {
		if (owner != newScene)
			onSceneSwitch(owner, newScene);
		owner = newScene;
	}
	
	/**
	 * Called when a scene node switches from one scene to another
	 * @param from Old scene
	 * @param to New scene
	 */
	protected void onSceneSwitch(Scene from, Scene to) {
		
	}
	
	public Scene getScene() {
		return owner;
	}
	
	/**
	 * @return true if this scene node (or any of its parent) has been moved
	 */
	public boolean isDirty() {
		return isDirty;
	}
	
	/**
	 * resets the dirty flag to 'false'
	 */
	public void unsetDirtyFlag() {
		isDirty = false;
	}
	
	/**
	 * Return the local transformation relative to the parent
	 * 
	 * @return Matrix (local tranformation)
	 */
	public Matrix getLocalTransformation() {
		return localTransformation;
	}
	
	/**
	 * Calculates and returns the world transformation
	 * 
	 * @return Matrix (world transformation)
	 */
	public Matrix getWorldTransformation() {
		// quick and dirty...
		Matrix.assign(worldTransformation.v, localTransformation.v);
		
		SceneNode next = parentNode;
		while (next != null) {
			Matrix.preMultiplyMM(worldTransformation.v, next.localTransformation.v);
			next = next.parentNode;
		}
		
		return worldTransformation;
	}
		
	/**
	 * Translates (moves) this scene node
	 * 
	 * @param x
	 * @param y
	 * @param z
	 */
	public void translate(float x, float y, float z) {
		Matrix.translateM(localTransformation.v, 0, x, y, z);
		isDirty = true;
	}
		
	/**
	 * Rotates this scene node around its center
	 * 
	 * @param deg
	 * @param x
	 * @param y
	 * @param z
	 */
	public void rotate(float deg, float x, float y, float z) {
		Matrix.rotateM(localTransformation.v, 0, deg, x, y, z);
	}
		
	/**
	 * Checks if this node has any children
	 * 
	 * @return true if there are children, otherwise false
	 */
	public boolean hasChildren() {
		return childNodes.size()>0;
	}
	
	public ArrayList<SceneNode> getChildren() {
		return childNodes;
	}
	
	public boolean checkCulling() {
		// currently always visible
		return true;
	}
	
	private void setParentNode(SceneNode newParent) {
		parentNode = newParent;
		Scene newScene = null;
		if (newParent != null)
			newScene = newParent.owner;
		
		if (newScene != owner)
			setOwningSceneRecursively(newScene);
	}
	
	void setOwningSceneRecursively(Scene set) {
		setScene(set);
		for (SceneNode curNode : childNodes) {
			curNode.setOwningSceneRecursively(set);
		}
	}
	
		
	/**
	 * Detaches this node from its parent.
	 */
	public void detach() {
		if (parentNode != null) {
			parentNode.detachChild(this);
		} else {
			owner.removeChild(this);
		}
	}
	
	/**
	 * Attaches this node to a new parent.
	 * @param newParent
	 */
	public void attachTo(SceneNode newParent) {
		newParent.attachChild(this);
	}
	
	/**
	 * Attaches a node as child. Detaches it if it has already a parent.
	 * @param node
	 */
	public void attachChild(SceneNode node) {
		if (node.parentNode != null)
			node.parentNode.detachChild(node);
		
		node.setParentNode(this);
		childNodes.add(node);
	}
	
	/**
	 * Detaches a specific child.
	 * @param child
	 */
	public void detachChild(SceneNode child) {
		if (childNodes.contains(child))
		{
			child.parentNode = null;
			childNodes.remove(child);
		}
		
	}
	
}
